package QHgui.docing;

import com.javadocking.dockable.CompositeDockable;
import com.javadocking.dockable.Dockable;
import com.javadocking.dockable.DockableState;
import com.javadocking.dockable.action.DefaultDockableStateAction;
import com.javadocking.dockable.action.DockableStateAction;
import com.javadocking.dockable.action.PopupMenuFactory;
import com.javadocking.util.DockingUtil;

import javax.swing.*;
import java.util.ArrayList;
import java.util.List;

/**
 * <p> TODU </P>
 *
 * @author yeager
 * @Version V1.1.1.0
 * @date 2023/11/24 10:29
 */
public class SPopupMenuFactory implements PopupMenuFactory {

    // Static fields.

    /** The constant that defines that the actions of the dockable have to be added to the popup menu.
     * The actions of the dockable are retrieved by {@link Dockable#getActions()}. */
    public static final int	DOCKABLE_ACTIONS		= 1;
    /** The constant that defines that the close all action has to be added to the popup menu. */
    public static final int	CLOSE_ALL_ACTION		= 1<<1;
    /** The constant that defines that the close others action has to be added to the popup menu. */
    public static final int	CLOSE_OTHERS_ACTION		= 1<<2;
    /** The constant that defines that the minimize all action has to be added to the popup menu. */
    public static final int	MINIMIZE_ALL_ACTION		= 1<<3;
    /** The constant that defines that the minimize others action has to be added to the popup menu. */
    public static final int	MINIMIZE_OTHERS_ACTION	= 1<<4;
    /** The constant that defines that the restore all action has to be added to the popup menu. */
    public static final int	RESTORE_ALL_ACTION		= 1<<5;

    // Fields.

    /** This integer defines which actions will be added to te popup menu.
     * The  constants DOCKABLE_ACTIONS, CLOSE_ALL_ACTION, CLOSE_OTHERS_ACTION,
     * MINIMIZE_ALL_ACTION, and MINIMIZE_OTHERS_ACTION can be combined with the bitwise or-operation. */
    private int 			popupActions		= Integer.MAX_VALUE;

    // Implementations of PopupMenuFactory.

    public JPopupMenu createPopupMenu(Dockable selectedDockable, CompositeDockable compositeDockable)
    {

        JPopupMenu popupMenu = new JPopupMenu();
        int count = 0;
        int currentState = -1;

        // Create the actions for the selected dockable.
        if ((selectedDockable != null) && ((DOCKABLE_ACTIONS & popupActions) != 0))
        {

            currentState = selectedDockable.getState();

            // We have to get the wrapper around the dockable with all the actions.
            Dockable dockable = DockingUtil.retrieveDockableOfDockModel(selectedDockable.getID());
            if (dockable == null)
            {
                throw new IllegalStateException("The dragged dockable should be docked in the dock model.");
            }

            Action[][] actionMatrix = dockable.getActions();

            // Do we have actions?
            if (actionMatrix == null)
            {
                return null;
            }

            // Iterate over the rows.
            boolean firstGroup = true;
            for (int group = 0; group < actionMatrix.length; group++) {

                Action[] actionGroup = actionMatrix[group];
                if ((actionGroup != null) && (actionGroup.length > 0))
                {
                    // Are we in the first group?
                    if (firstGroup)
                    {
                        firstGroup = false;
                    }
                    else
                    {
                        popupMenu.addSeparator();
                    }

                    // Add all the actions of the group.
                    for (int index = 0; index < actionGroup.length; index++)
                    {
                        popupMenu.add(new JMenuItem(actionGroup[index]));
                        count++;
                    }
                }
            }

        }

        // Create the actions for the composite dockable.
        if ((compositeDockable != null) && (compositeDockable.getDockableCount() > 0))
        {
            if ((compositeDockable.getSelectedDockable() == null) || (compositeDockable.getDockableCount() > 1))
            {
                DockableStateAction closeAllAction = null;
                DockableStateAction minimizeAllAction = null;
                DockableStateAction restoreAllAction = null;
                DockableStateAction closeOthersAction = null;
                DockableStateAction minimizeOthersAction = null;

                // We create the 2 actions: close all and minimize all.
                if ((currentState != DockableState.CLOSED) && (CLOSE_ALL_ACTION & popupActions) != 0)
                {
                    if (createAction(DockableState.CLOSED, compositeDockable))
                    {
                        closeAllAction = new SCloseAllDockableStateAction(compositeDockable, null);
                    }
                }
                if ((currentState != DockableState.MINIMIZED) && (MINIMIZE_ALL_ACTION & popupActions) != 0)
                {
                    if (createAction(DockableState.MINIMIZED, compositeDockable))
                    {
                        minimizeAllAction = new DefaultDockableStateAction(compositeDockable, DockableState.MINIMIZED, "Minimize All", null);
                    }
                }
                if ((currentState != DockableState.NORMAL) && (RESTORE_ALL_ACTION & popupActions) != 0)
                {
                    if (createAction(DockableState.NORMAL, compositeDockable))
                    {
                        restoreAllAction = new DefaultDockableStateAction(compositeDockable, DockableState.NORMAL, "Restore All", null);
                    }
                }

                // Is there a selected dockable?
                if ((compositeDockable.getSelectedDockable() != null) && (compositeDockable.getDockableCount() > 1))
                {
                    List otherDockablesList = new ArrayList();
                    Dockable selectedDockableOfComposite = compositeDockable.getSelectedDockable();
                    for (int index = 0; index < compositeDockable.getDockableCount(); index++)
                    {
                        Dockable dockableToAdd = compositeDockable.getDockable(index);
                        if (!dockableToAdd.equals(selectedDockableOfComposite))
                        {
                            otherDockablesList.add(dockableToAdd);
                        }
                    }
                    Dockable[] otherDockables = new Dockable[compositeDockable.getDockableCount() - 1];
                    otherDockables = (Dockable[])otherDockablesList.toArray(otherDockables);
                    closeOthersAction = new SCloseOtherDockableStateAction(selectedDockableOfComposite, null);

                }

                // Add the action.
                if ((count > 0) &&
                        ((closeAllAction != null) ||
                                (restoreAllAction != null) ||
                                (closeOthersAction != null) ||
                                (minimizeAllAction != null) ||
                                (minimizeOthersAction != null)))
                {
                    popupMenu.addSeparator();
                }
                if (closeAllAction != null)
                {
                    count++;
                    popupMenu.add(new JMenuItem(closeAllAction));
                }
                if (closeOthersAction != null)
                {
                    count++;
                    popupMenu.add(new JMenuItem(closeOthersAction));
                }
                if (minimizeAllAction != null)
                {
                    count++;
                    popupMenu.add(new JMenuItem(minimizeAllAction));
                }
                if (minimizeOthersAction != null)
                {
                    count++;
                    popupMenu.add(new JMenuItem(minimizeOthersAction));
                }
                if (restoreAllAction != null)
                {
                    count++;
                    popupMenu.add(new JMenuItem(restoreAllAction));
                }

            }
        }

        // Return the popup menu, if it has any items.
        if (count > 0)
        {
            return popupMenu;
        }
        return null;

    }

    // Getters / Setters.

    /**
     * Gets the integer that defines which actions will be added to te popup menu.
     * This is a bitwise or of the constants DOCKABLE_ACTIONS, CLOSE_ALL_ACTION, CLOSE_OTHERS_ACTION,
     * MINIMIZE_ALL_ACTION, and MINIMIZE_OTHERS_ACTION.
     *
     * @return					The integer that defines which actions will be added to te popup menu.
     */
    public int getPopupActions()
    {
        return popupActions;
    }

    /**
     * Sets the integer that defines which actions will be added to te popup menu.
     *
     * @param 	popupActions	The integer that defines which actions will be added to te popup menu.
     * 							The  constants DOCKABLE_ACTIONS, CLOSE_ALL_ACTION, CLOSE_OTHERS_ACTION,
     * 							MINIMIZE_ALL_ACTION, and MINIMIZE_OTHERS_ACTION can be combined with the bitwise or-operation.
     */
    public void setPopupActions(int popupActions)
    {
        this.popupActions = popupActions;
    }

    // Private metods.

    /**
     * True is returned when all the following conditions are true for all the child dockables:
     * <ul>
     * <li>The new state is a valid state for every dockable.</li>
     * <li>Every dockable of the composite has an action to convert to the new state.</li>
     * <li>Every dockable is in a different state as the new state.</li>
     * </ul>
     */
    private boolean createAction(int newState, CompositeDockable compositeDockable)
    {

        for (int index = 0; index < compositeDockable.getDockableCount(); index++)
        {
            Dockable childDockable = compositeDockable.getDockable(index);
            // Is the new state a valid state for the dockable?
            if ((childDockable.getPossibleStates() & newState) == 0)
            {
                return false;
            }
            // The new state is different from the current state for this child dockable.
            if (childDockable.getState() == newState)
            {
                return false;
            }
            // Has the dockable an action to convert to the new state.
            if (!hasDockableStateAction(childDockable, newState))
            {
                return false;
            }
        }

        // The new state is not valid for any child.
        return true;

    }

    /**
     * Determines if the dockable has a an action to change the dockable to the new state ({@link DockableStateAction}).
     *
     * @param 	newState	The state for which the dockable can have an action.
     * @return				True when the dockable has an action to go to the given state.
     */
    private boolean hasDockableStateAction(Dockable dockable, int newState)
    {

        Action[][] actionMatrix = dockable.getActions();
        if (actionMatrix == null)
        {
            return false;
        }
        for (int row = 0; row < actionMatrix.length; row++)
        {
            Action[] actionRow = actionMatrix[row];
            if (actionRow != null)
            {
                for (int column = 0; column < actionRow.length; column++)
                {
                    Action action = actionRow[column];
                    if (action instanceof DockableStateAction)
                    {
                        DockableStateAction dockableStateAction = (DockableStateAction)action;
                        if (dockableStateAction.getNewDockableState() == newState)
                        {
                            return true;
                        }
                    }
                }
            }
        }
        return false;

    }
}
